package com.azzcs.spring.factory.config;

import com.azzcs.spring.anno.Autowired;
import com.azzcs.spring.anno.Transaction;
import com.azzcs.spring.bean.TransactionManager;
import com.azzcs.spring.factory.BeanFactory;
import com.azzcs.spring.factory.DefaultListableBeanFactory;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author: wzg
 * @Date: 2020/12/20 上午10:42
 */
public class TransactionBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
    private DefaultListableBeanFactory beanFactory;
    @Autowired
    private TransactionManager transactionManager;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        Class<?>[] interfaces = bean.getClass().getInterfaces();
        if(interfaces.length > 0){
            return jdkProxy(bean);
        }
        return cglibProxy(bean);
    }

    private Object cglibProxy(Object bean) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback(new CglibMethodInterceptor(bean));
        return enhancer.create();
    }

    private Object jdkProxy(Object bean){
        return Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(), new JdkMethodInterceptor(bean));
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    @Override
    public int compareTo(Object o) {
        return 1;
    }
    private class JdkMethodInterceptor implements InvocationHandler{
        private Object bean;

        public JdkMethodInterceptor(Object bean) {
            this.bean = bean;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Method beanMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
            if (method.isAnnotationPresent(Transaction.class)
                    || method.getDeclaringClass().isAnnotationPresent(Transaction.class)
                    || bean.getClass().isAnnotationPresent(Transaction.class)
                    || beanMethod.isAnnotationPresent(Transaction.class)) {
                Object result =null;
                try {
                    transactionManager.open();
                    result = method.invoke(bean, args);

                } catch (Exception e) {
                    transactionManager.rollback();
                    throw e;
                }
                transactionManager.commit();
                return result;
            }
            return method.invoke(bean, args);

        }
    }

    private class CglibMethodInterceptor implements MethodInterceptor {
        private Object bean;

        public CglibMethodInterceptor(Object bean) {
            this.bean = bean;
        }

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            Method beanMethod = bean.getClass().getMethod(method.getName(), method.getParameterTypes());
            if (method.isAnnotationPresent(Transaction.class)
                    || method.getDeclaringClass().isAnnotationPresent(Transaction.class)
                    || bean.getClass().isAnnotationPresent(Transaction.class)
                    || beanMethod.isAnnotationPresent(Transaction.class)) {
                Object result =null;
                try {
                    transactionManager.open();
                    result = method.invoke(bean, objects);

                } catch (Exception e) {
                    transactionManager.rollback();
                    throw e;
                }
                transactionManager.commit();
                return result;
            }
            return method.invoke(bean, objects);
        }
    }
}
